`include "include.v"

module omicron_tb();

reg rst_n;
reg clk_in;
wire [7:0] leds;

// INSTRUCTION_FETCH
wire [15:0] if_curr_inst;
wire [6:0]  m_branch_addr;
wire        m_branch_en;
wire [6:0]  if_next_addr;
wire [6:0]  pc;

// INSTRUCTION_DECODE
wire        wb_reg_wea;
wire [15:0] wb_reg_wdata;
wire [2:0]  wb_reg_waddr;
wire [3:0]  id_opcode;          // [15:12]
wire [6:0]  id_next_addr;
wire [15:0] id_register1_data;
wire [15:0] id_register2_data;
wire [6:0]  id_sign_ext_addr;   // [6:0]
wire [2:0]  id_dest_reg_addr;   // [8:6]

// EXECUTE
wire [3:0]  cu_alu_opcode;
wire        cu_alu_sel_b;
wire [6:0]  ex_sign_ext_next_addr;
wire        ex_alu_z;
wire [15:0] ex_alu_result;
wire [15:0] ex_register2_data;
wire [2:0]  ex_reg_waddr;

// MEMORY
wire        cu_dm_wea;
wire [1:0]  cu_branch;
wire [15:0] m_alu_result;
wire [15:0] m_dm_dout;
wire [2:0]  m_reg_waddr;

// WRITE_BACK
wire        cu_reg_data_loc;
wire        cu_reg_load;

// REG_BLOCK
wire [15:0] reg_1;
wire [15:0] reg_2;
wire [15:0] reg_3;
wire [15:0] reg_4;
wire [15:0] reg_5;
wire [15:0] reg_6;
wire [15:0] reg_7;

// INSTRUCTION_FETCH
assign pc[6:0]                    = DUT.i_omicron.i_data_path.i_instruction_fetch.pc[6:0];
assign if_curr_inst[15:0]         = DUT.i_omicron.i_data_path.i_instruction_fetch.if_curr_inst[15:0];
assign m_branch_addr[6:0]         = DUT.i_omicron.i_data_path.i_instruction_fetch.m_branch_addr[6:0];
assign m_branch_en                = DUT.i_omicron.i_data_path.i_instruction_fetch.m_branch_en;
assign if_next_addr[6:0]          = DUT.i_omicron.i_data_path.i_instruction_fetch.if_next_addr[6:0];

// INSTRUCTION_DECODE
assign wb_reg_wea                 = DUT.i_omicron.i_data_path.i_instruction_decode.wb_reg_wea;
assign wb_reg_wdata[15:0]         = DUT.i_omicron.i_data_path.i_instruction_decode.wb_reg_wdata[15:0];
assign wb_reg_waddr[2:0]          = DUT.i_omicron.i_data_path.i_instruction_decode.wb_reg_waddr[2:0];
assign id_opcode[3:0]             = DUT.i_omicron.i_data_path.i_instruction_decode.id_opcode[3:0];
assign id_next_addr[6:0]          = DUT.i_omicron.i_data_path.i_instruction_decode.id_next_addr[6:0];
assign id_register1_data[15:0]    = DUT.i_omicron.i_data_path.i_instruction_decode.id_register1_data[15:0];
assign id_register2_data[15:0]    = DUT.i_omicron.i_data_path.i_instruction_decode.id_register2_data[15:0];
assign id_sign_ext_addr[6:0]      = DUT.i_omicron.i_data_path.i_instruction_decode.id_sign_ext_addr[6:0];
assign id_dest_reg_addr[2:0]      = DUT.i_omicron.i_data_path.i_instruction_decode.id_dest_reg_addr[2:0];

// EXECUTE
assign cu_alu_opcode[3:0]         = DUT.i_omicron.i_data_path.i_execute.cu_alu_opcode[3:0];
assign cu_alu_sel_b               = DUT.i_omicron.i_data_path.i_execute.cu_alu_sel_b;
assign ex_sign_ext_next_addr[6:0] = DUT.i_omicron.i_data_path.i_execute.ex_sign_ext_next_addr[6:0];
assign ex_alu_z                   = DUT.i_omicron.i_data_path.i_execute.ex_alu_z;
assign ex_alu_result[15:0]        = DUT.i_omicron.i_data_path.i_execute.ex_alu_result[15:0];
assign ex_register2_data[15:0]    = DUT.i_omicron.i_data_path.i_execute.ex_register2_data[15:0];
assign ex_reg_waddr[2:0]          = DUT.i_omicron.i_data_path.i_execute.ex_reg_waddr[2:0];

// MEMORY
assign cu_dm_wea                  = DUT.i_omicron.i_data_path.i_memory.cu_dm_wea;
assign cu_branch[1:0]             = DUT.i_omicron.i_data_path.i_memory.cu_branch[1:0];
assign m_alu_result[15:0]         = DUT.i_omicron.i_data_path.i_memory.m_alu_result[15:0];
assign m_dm_dout[15:0]            = DUT.i_omicron.i_data_path.i_memory.m_dm_dout[15:0];
assign m_reg_waddr[2:0]           = DUT.i_omicron.i_data_path.i_memory.m_reg_waddr[2:0];

// WRITE_BACK
assign cu_reg_data_loc            = DUT.i_omicron.i_data_path.i_write_back.cu_reg_data_loc;
assign cu_reg_load                = DUT.i_omicron.i_data_path.i_write_back.cu_reg_load;

// REG_BLOCK
assign reg_1                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h1];
assign reg_2                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h2];
assign reg_3                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h3];
assign reg_4                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h4];
assign reg_5                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h5];
assign reg_6                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h6];
assign reg_7                      = DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h7];

initial begin
  `ifndef IVERILOG
    $vcdpluson(omicron_tb);
    $vcdplusmemon(DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers);
    $vcdplusmemon(DUT.i_omicron.i_data_path.i_instruction_fetch.i_inst_mem.imem);
    $vcdplusmemon(DUT.i_omicron.i_data_path.i_memory.i_data_mem.dmem);
  `else
    $dumpfile("omicron_tb.vcd");
    $dumpvars(0,omicron_tb);
  `endif
  $readmemb("imem.txt", DUT.i_omicron.i_data_path.i_instruction_fetch.i_inst_mem.imem);
  $readmemh("dmem.txt", DUT.i_omicron.i_data_path.i_memory.i_data_mem.dmem);
end

initial begin
  $display("******** omicron_tb start of simulation *********");
  rst_n = 0;
  clk_in = 0;
  #100 rst_n = 1;
  @(posedge clk_in);
  @(posedge clk_in);
  while (if_curr_inst !== 16'hXXXX) begin
    @(posedge clk_in);
  end
  $display("******** omicron_tb finish of simulation *********");
  $finish;
end

always #20 clk_in = ~clk_in;

always @(pc) begin
  if (if_curr_inst !== 16'hxxxx) begin
    instruction_decode(if_curr_inst, pc);
  end
end

always @(reg_1 or reg_2 or reg_3 or reg_4 or reg_5 or reg_6 or reg_7) begin
  print_reg_blk;
end


omicron_top DUT (
  .rst_n(rst_n),
  .clk_in(clk_in),
  .leds(leds[7:0])
);

task instruction_decode;
  input [15:0] inst;
  input [6:0]  pc;
begin
  //$display("%16b", inst);
  case (inst[15:12])
    4'b0000 : 
    begin
      case(inst[2:0])
        3'b000 : $display("[%2X] NOOP"                        , pc);
        3'b001 : $display("[%2X] CPY  $%1X <- $%1X"           , pc, inst[8:6], inst[11:9]);
        3'b010 : $display("[%2X] ADD  $%1X <- $%1X +    $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b011 : $display("[%2X] SUB  $%1X <- $%1X -    $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b100 : $display("[%2X] MUL  $%1X <- $%1X *    $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b101 : $display("[%2X] AND  $%1X <- $%1X and  $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b110 : $display("[%2X] OR   $%1X <- $%1X or   $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b111 : $display("[%2X] NOT  $%1X <- ~$%1X"          , pc, inst[8:6], inst[8:6]);
        default: $display("[%2X] ERROR: Invalid function: %3b", pc, inst[2:0]);
      endcase
    end
    4'b0001 :
    begin
      case(inst[2:0])
        3'b000 : $display("[%2X] XOR  $%1X <- $%1X xor  $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b001 : $display("[%2X] NOR  $%1X <- $%1X nor  $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b010 : $display("[%2X] XNOR $%1X <- $%1X xnor $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b011 : $display("[%2X] LS   $%1X <- $%1X  <<  $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b100 : $display("[%2X] RS   $%1X <- $%1X  >>  $%1X" , pc, inst[8:6], inst[11:9], inst[5:3]);
        3'b101 : $display("[%2X] INC  $%1X <- $%1X  +   1"    , pc, inst[8:6], inst[8:6]);
        3'b110 : $display("[%2X] DEC  $%1X <- $%1X  -   1"    , pc, inst[8:6], inst[8:6]);
        3'b111 : $display("[%2X] ZERO $%1X <- 0"              , pc, inst[8:6]);
        default: $display("[%2X] ERROR: Invalid function: %3b", pc, inst[2:0]);
      endcase
    end
    `BEQ_i  : $display("[%2X] BEQ ", pc);
    `BNE_i  : $display("[%2X] BNE ", pc);
    `LD_i   : $display("[%2X] LD   $%1X    <- M[%02X]", pc, inst[8:6], inst[5:0]);
    `STR_i  : $display("[%2X] STR  M[%02X] <- $%1X"   , pc, inst[5:0], inst[8:6]);
    `ADDI_i : $display("[%2X] ADDI", pc);
    `SUBI_i : $display("[%2X] SUBI", pc);
    `JMP_i  : $display("[%2X] JMP ", pc);
    default : $display("[%2X] ERROR: Invalid opcode: %4b", pc, inst[15:12]);
  endcase
end
endtask

task print_reg_blk;
begin
  $display("-------------------------------------------------------------------------------------------------");
  $display("    $1 => %4X, $2 => %4X, $3 => %4X, $4 => %4X, $5 => %4X, $6 => %4X, $7 => %4X", 
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h1],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h2],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h3],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h4],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h5],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h6],
                         DUT.i_omicron.i_data_path.i_instruction_decode.i_reg_block.registers[3'h7]);
  $display("-------------------------------------------------------------------------------------------------");
end
endtask

endmodule
